package com.smartfast4j.springboot.redis.demo.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Created by gacl on 2017/11/20.
 */
@Repository
public class RedisDao {

    /**
     * RedisTemplate中定义了对5种数据结构操作
     redisTemplate.opsForValue();//操作字符串
     redisTemplate.opsForHash();//操作hash
     redisTemplate.opsForList();//操作list
     redisTemplate.opsForSet();//操作set
     redisTemplate.opsForZSet();//操作有序set
     */
    //@Autowired
    //private StringRedisTemplate redisTemplate;

    /**
     * RedisTemplate 不能按类型装配注入
     *  @Autowired
        private RedisTemplate<String,Object> redisTemplate;
     * Caused by: org.springframework.beans.factory.
     * UnsatisfiedDependencyException: Error creating bean with name 'redisDao': Unsatisfied dependency expressed through field 'redisTemplate';
     * nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
     * No qualifying bean of type 'org.springframework.data.redis.core.RedisTemplate<java.lang.String, java.lang.Object>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
     *
     * 模板中的Redis key的类型（通常为String）如：RedisTemplate<String, Object>
     * 注意：如果没特殊情况，切勿定义成RedisTemplate<Object, Object>，否则根据里氏替换原则，使用的时候会造成类型错误 。
     *
     * StringRedisTemplate与RedisTemplate
        两者的关系是StringRedisTemplate继承RedisTemplate。
        两者的数据是不共通的；也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据，RedisTemplate只能管理RedisTemplate中的数据。
        SDR默认采用的序列化策略有两种，一种是String的序列化策略，一种是JDK的序列化策略。
        StringRedisTemplate默认采用的是String的序列化策略，保存的key和value都是采用此策略序列化保存的。
        RedisTemplate默认采用的是JDK的序列化策略，保存的key和value都是采用此策略序列化保存的。
     */
    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * 将value对象写入缓存
     * @param key
     * @param value
     * @param time 失效时间(秒)
     */
    public void set(String key,Object value,int time){
        redisTemplate.opsForValue().set(key, value);
        if(time> 0) {
            //成功返回true，失败返回false
            redisTemplate.expire(key, time, TimeUnit.SECONDS);
        }
    }

    /**
     * 将value对象写入缓存
     * @param key
     * @param value
     */
    public void set(String key,Object value){
       set(key,value,0);
    }

    /**
     * 取得缓存（int型）
     * @param key
     * @return
     */
    public Integer getInt(String key){
        return get(key,Integer.class);
    }

    /**
     * 取得缓存（字符串类型）
     * @param key
     * @return
     */
    public String getStr(String key){
        return get(key, String.class);
    }

    /**
     * 取得缓存（字符串类型）
     * @param key
     * @param retain 是否保留
     * @return
     */
    public String getStr(String key, boolean retain){
        String value = get(key, String.class);
        if(!retain){
            redisTemplate.delete(key);
        }
        return value;
    }

    /**
     * 获取缓存<br>
     * 注：基本数据类型(Character除外)，请直接使用get(String key, Class<T> clazz)取值
     * @param key
     * @return
     */
    public  Object getObj(String key){
        return redisTemplate.boundValueOps(key).get();
    }

    /**
     * 获取缓存<br>
     * 注：java 8种基本类型的数据请直接使用get(String key, Class<T> clazz)取值
     * @param key
     * @param retain    是否保留
     * @return
     */
    public Object getObj(String key, boolean retain){
        Object obj = redisTemplate.boundValueOps(key).get();
        if(!retain){
            redisTemplate.delete(key);
        }
        return obj;
    }

    /**
     * 获取缓存<br>
     * 注：该方法暂不支持Character数据类型
     * @param key   key
     * @param clazz 类型
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> T get(String key, Class<T> clazz) {
        return (T)redisTemplate.boundValueOps(key).get();
    }

    /**
     * 指定缓存的失效时间
     *
     * @param key 缓存KEY
     * @param time 失效时间(秒)
     */
    public void expire(String key, int time) {
        if(time> 0){
            redisTemplate.expire(key, time, TimeUnit.SECONDS);
        }
    }

    /**
     * 删除缓存<br>
     * 根据key精确匹配删除
     * @param key
     */
    @SuppressWarnings("unchecked")
    public void del(String... key){
        if(key!=null && key.length > 0){
            if(key.length == 1){
                redisTemplate.delete(key[0]);
            }else{
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    /**
     * 批量删除<br>
     * （该操作会执行模糊查询，请尽量不要使用，以免影响性能或误删）
     * @param pattern
     */
    public void batchDel(String... pattern){
        for (String kp : pattern) {
            redisTemplate.delete(redisTemplate.keys(kp + "*"));
        }
    }

    /**
     * 将map写入缓存
     * @param key
     * @param map
     */
    public <T> void addMap(String key, Map<String, T> map,int time){
        redisTemplate.opsForHash().putAll(key, map);
        expire(key,time);
    }

    /**
     * 向key对应的map中添加缓存对象
     * @param key
     * @param map
     */
    public <T> void addMap(String key, Map<String, T> map){
        redisTemplate.opsForHash().putAll(key, map);
    }

    /**
     * 向key对应的map中添加缓存对象
     * @param key   cache对象key
     * @param field map对应的key
     * @param value     值
     */
    public void addMap(String key, String field, String value){
        redisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 向key对应的map中添加缓存对象
     * @param key   cache对象key
     * @param field map对应的key
     * @param obj   对象
     */
    public <T> void addMap(String key, String field, T obj){
        redisTemplate.opsForHash().put(key, field, obj);
    }

    /**
     * 获取map缓存
     * @param key
     * @param clazz
     * @return
     */
    public <T> Map<String, T> mget(String key, Class<T> clazz){
        BoundHashOperations<String, String, T> boundHashOperations = redisTemplate.boundHashOps(key);
        return boundHashOperations.entries();
    }

    /**
     * 获取map缓存中的某个对象
     * @param key
     * @param field
     * @param clazz
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> T getMapField(String key, String field, Class<T> clazz){
        return (T)redisTemplate.boundHashOps(key).get(field);
    }

    /**
     * 删除map中的某个对象
     * @param key   map对应的key
     * @param field map中该对象的key
     */
    public void delMapField(String key, String... field){
        BoundHashOperations<String, String, ?> boundHashOperations = redisTemplate.boundHashOps(key);
        boundHashOperations.delete(field);
    }

    /**
     * 模糊查询keys
     * @param pattern
     * @return
     */
    public Set<String> keys(String pattern){
        return redisTemplate.keys(pattern + "*");
    }

    /**
     * 清空Redis数据
     */
    public void  flushDB() {
        redisTemplate.execute(new RedisCallback() {
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                connection.flushDb();
                return "ok";
            }
        });
    }

    public Long dbSize() {
        return (Long)redisTemplate.execute(new RedisCallback() {
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.dbSize();
            }
        });
    }

    /**
     * 发送消息
     * @param channel 消息通道
     * @param message 要发送的消息
     */
    public void convertAndSend(String channel, Object message){
        redisTemplate.convertAndSend(channel,message);
    }
}
